package de.upb.aquarium.water.bubbles;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import javax.media.opengl.GL;

import com.sun.opengl.util.texture.Texture;
import com.sun.opengl.util.texture.TextureIO;

import de.upb.aquarium.api.objects.IPaintable;
import de.upb.aquarium.api.utility.VectorDouble;

public class BubbleManager implements IPaintable {
	
	private static BubbleManager instance;
	
	public static BubbleManager getInstance()
	{
		if (instance == null)
		{
			try
			{
				instance = new BubbleManager();
			}
			catch (IOException e)
			{
				System.out.println("Could not load bubble texture. Aborting.");
				System.exit(1);
			}
		}
		
		return instance;
	}
	
	private final VectorDouble surfacePlane;
	private final List<WaterBubble> normalBubbles;
	private final List<WaterBubble> pumpBubbles;
	private final List<BubbleSource> bubbleSources;
	
	private final Texture normalBubbleTexture;
	private final Texture pumpBubbleTexture;

	public BubbleManager() throws IOException {
		surfacePlane	= new VectorDouble(new double[] {0.0, 1.0, 0.0, 0.0});
		normalBubbles	= new LinkedList<WaterBubble>();
		pumpBubbles		= new LinkedList<WaterBubble>();
		bubbleSources	= new LinkedList<BubbleSource>();
		
		normalBubbleTexture = TextureIO.newTexture(new File("./textures/bubble.png"), true);
		pumpBubbleTexture	= TextureIO.newTexture(new File("./textures/drop.png"), true);
	}

	@Override
	public void paint(GL gl) {
		update();
		
		// All bubbles are transparent:
		gl.glEnable(GL.GL_BLEND);
		gl.glDisable(GL.GL_CULL_FACE);
		gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
		gl.glDepthMask(false);
		gl.glDisable(GL.GL_LIGHTING);
		
		gl.glEnable(GL.GL_TEXTURE_2D);
		normalBubbleTexture.bind();
		for (WaterBubble bubble : normalBubbles)
		{
			bubble.paint(gl);
		}
		
		pumpBubbleTexture.bind();
		for (WaterBubble bubble : pumpBubbles)
		{
			bubble.paint(gl);
		}
		
		gl.glDisable(GL.GL_TEXTURE_2D);

		gl.glDisable(GL.GL_BLEND);
		gl.glDepthMask(true);
		gl.glEnable(GL.GL_LIGHTING);
	}

	private void update() {
		
		// Loop through all bubbles, update their position and collect
		// bubbles to be deleted.
		List<WaterBubble> toRemove = new LinkedList<WaterBubble>();
		for (WaterBubble bubble : normalBubbles)
		{
			bubble.updatePosition();

			if (isToHigh(bubble))
			{
				toRemove.add(bubble);
			}
		}
		normalBubbles.removeAll(toRemove);
		
		
		toRemove.clear();
		for (WaterBubble bubble : pumpBubbles)
		{
			bubble.updatePosition();
			
			if (isToHigh(bubble))
			{
				toRemove.add(bubble);
			}
		}
		pumpBubbles.removeAll(toRemove);
		
		for (BubbleSource source : bubbleSources)
		{
			if (source instanceof PointBubbleSource)
			{
				normalBubbles.addAll(source.generate());
			}
			else
			{
				pumpBubbles.addAll(source.generate());
			}
		}
	}
	
	private boolean isToHigh(WaterBubble bubble) {
		float[] pos = bubble.getPosition();
		
		double result = 0.0;
		for (int i = 0; i < 3; i++)
		{
			result += pos[i]*surfacePlane.data[i];
		}
		
		result += surfacePlane.data[3];
		
		return result >= 0.0;
	}

	public void setSurfacePlane(double x, double y, double z, double dist)
	{
		surfacePlane.data[0] = x;
		surfacePlane.data[1] = y;
		surfacePlane.data[2] = z;
		surfacePlane.data[3] = dist;
	}
	
	public VectorDouble getSurfacePlane()
	{
		return surfacePlane;
	}
	
	public void addBubbleSource(BubbleSource source)
	{
		bubbleSources.add(source);
	}
	
	public void removeBubbleSource(BubbleSource source)
	{
		bubbleSources.remove(source);
	}
	
	public int getNumBubbles()
	{
		return normalBubbles.size() + pumpBubbles.size();
	}
}
